package com.drg.center.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.drg.center.dao.*;
import com.drg.center.entity.*;
import com.drg.center.entity.enums.ResTypeEnum;
import com.drg.center.entity.enums.UserStatusEnum;
import com.drg.center.entity.param.SysLoginDTO;
import com.drg.center.entity.result.*;
import com.google.common.collect.Maps;
import com.guaike.core.entity.constant.ViewConstant;
import com.guaike.core.entity.context.ContextHolder;
import com.guaike.core.entity.enums.DataPermissonTypeEnum;
import com.guaike.core.entity.enums.UserAccTypeEnum;
import com.guaike.core.entity.enums.VerifyCodeType;
import com.guaike.core.entity.exception.ApiException;
import com.guaike.core.entity.result.Result;
import com.guaike.core.entity.result.User;
import com.guaike.core.redis.constant.CacheKeyConstant;
import com.guaike.core.redis.context.ApiContext;
import com.guaike.core.utils.*;
import com.drg.center.service.SysLoginService;
import com.drg.center.service.SysResourcesService;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.assertj.core.util.Sets;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.awt.image.BufferedImage;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: zhangxilin
 * @Date: 2023/10/30
 */
@Slf4j
@Service
public class SysLoginServiceImpl implements SysLoginService {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SysResourcesService sysResourcesService;

    @Autowired
    private SysIndexDAO sysIndexDAO;
    @Autowired
    private SysResourcesDAO sysResourcesDAO;
    @Autowired
    private SysUserDAO sysUserDAO;
    @Autowired
    private SysUserScopeDAO sysUserScopeDAO;
    @Autowired
    private SysDepartDAO sysDeptDAO;
    @Autowired
    private SysUserRoleDAO sysUserRoleDAO;
    @Autowired
    private SysRoleDAO sysRoleDAO;
    @Autowired
    private SysRoleResourcesDAO sysRoleResourcesDAO;

    private final static Integer USER_PWD_FAIL_COUNT = 5;

    @Override
    public Result<SysLoginVO> login(SysLoginDTO param) {
        String password = decodeStr(param.getPassword());
        param.setPassword(password);
        if(!checkPicVerifyCode(param.getVerifyToken(),param.getVerifyCode())){
            return Result.failure().message("验证码错误").build();
        }

        SysUserPO record =  sysUserDAO.findByUsernameAndNotDisable(param.getUsername());
        if(record==null){
            log.error("用户名不存在，username:{)",param.getUsername());
            return Result.failure().message("用户名或密码错误,请重新输入").build();
        }
        if(record.getStatus().equals(UserStatusEnum.FROZEN.getCode())){
            log.error("账号已被冻结，username:{}",param.getUsername());
            return Result.failure().message("账号已被冻结,请联系管理员").build();
        }
        if(record.getFrozenExpire()> System.currentTimeMillis()){
            log.error("密码错误超5次，账号已被冻结，username:{}",param.getUsername());
            return Result.failure().message("账号已被冻结,请30分钟后登录").build();
        }


        if (CompareUtil.isEqual(record.getAccType(), UserAccTypeEnum.SYS_MANAGER.getType())) {
            return managerlogin(param,record);
        }
        return generalUserLogin(param,record);

    }

    private String decodeStr(String encodeStr){
        if(StringUtils.isBlank(encodeStr)){
            return null;
        }
        return Base64.decodeStr(encodeStr);
    }


    /**
     * 超管登录
     * @param param
     * @param record
     * @return
     */
    private Result<SysLoginVO> managerlogin(SysLoginDTO param,SysUserPO record){
        if(!checkPassword(param.getPassword(),record.getPassword())){
            log.error("当前账号为超级管理员账号, 密码错误，username: {},  password:{}",param.getUsername(),param.getPassword());
            return Result.failure().message("用户名或密码错误,请重新输入").build();
        }
        User user = new User();
        user.setUserId(record.getId());
        user.setUsername(record.getUsername());
        user.setRealName(record.getRealName());
        user.setAccType(record.getAccType());
        String accessToken = ApiContext.setCurrentUser(user, ContextHolder.getClientId());

        SysLoginVO vo = new SysLoginVO();
        vo.setAccessToken(accessToken);
        vo.setUsername(user.getUsername());
        vo.setRealName(user.getRealName());
        vo.setAccType(record.getAccType());
        vo.setSysCode(getSysCodeIfOnlyOne());
        vo.setPhone(record.getPhone());
        vo.setCreateTime(record.getCreateTime());
        return Result.success().data(vo).build();
    }

    private String getSysCodeIfOnlyOne(){
        List<SysIndexPO> poList = sysIndexDAO.findList();
        if(!CollectionUtils.isEmpty(poList) && poList.size()==1){
            return poList.get(0).getSysCode();
        }
        return null;
    }


    /**
     * 普通用户登录
     * @param param
     * @param record
     * @return
     */
    private Result<SysLoginVO> generalUserLogin(SysLoginDTO param,SysUserPO record){
        RAtomicLong rAtomicLong= redissonClient.getAtomicLong(this.loginFailCountKey(param.getUsername()));
        if(!checkPassword(param.getPassword(),record.getPassword())){
            Long count=rAtomicLong.incrementAndGet();
            rAtomicLong.expire(CacheKeyConstant.USER_FROZEN_EXPIRE, TimeUnit.SECONDS);
            if(count.intValue() >= USER_PWD_FAIL_COUNT){
                log.error("【用户登录】密码错误已达规定次数，账号被冻结30分钟 username: {}，password:{}",param.getUsername(),param.getPassword());
                record.setFrozenExpire(DateUtil.addSeconds(new Date(),CacheKeyConstant.USER_FROZEN_EXPIRE.intValue()).getTime());
                String user_pwd_fail_reason = String.format("密码输入错误超%s次", USER_PWD_FAIL_COUNT);
                record.setFrozenReason(user_pwd_fail_reason);
                sysUserDAO.updateById(record);
            }
            log.error("密码错误，username: {},  password:{}",param.getUsername(),param.getPassword());
            return Result.failure().message("用户名或密码错误,请重新输入").build();
        }
        rAtomicLong.delete();


        User user = addUser2Cache(record);

        SysLoginVO vo = new SysLoginVO();
        vo.setAccessToken(user.getAccessToken());
        vo.setUsername(user.getUsername());
        vo.setRealName(user.getRealName());
        vo.setAvatar(user.getAvatar());
        vo.setAccType(record.getAccType());
        vo.setSysCode(getSysCodeIfOnlyOne());
        vo.setPhone(record.getPhone());
        vo.setCreateTime(record.getCreateTime());
        vo.setDeptName(user.getDeptName());

        return Result.success().data(vo).build();
    }

    private User addUser2Cache(SysUserPO record){

        User user = new User();
        user.setUserId(record.getId());
        user.setUsername(record.getUsername());
        user.setRealName(record.getRealName());
        user.setDeptId(record.getDeptCode());
        user.setAccType(record.getAccType());
        user.setScopeType(record.getScopeType());
        SysDepartPO deptPO = null;
        if(StringUtils.isNotBlank(record.getDeptCode())){
            deptPO = sysDeptDAO.findOne(SysDepartPO::getDepartCode,  record.getDeptCode());
            user.setDeptName(deptPO !=null? deptPO.getDepartName() : null);
        }

        if(CompareUtil.isEqual(record.getScopeType(), DataPermissonTypeEnum.CUSTOM.getCode())){
            List<SysUserScopePO> userScopePOList = sysUserScopeDAO.findList(SysUserScopePO::getUserId,record.getId());
            if(!CollectionUtils.isEmpty(userScopePOList)){
                user.setScopeDeptIdList(userScopePOList.stream().map(SysUserScopePO::getDeptCode).collect(Collectors.toList()));
            }
        }else if(CompareUtil.isEqual(record.getScopeType(), DataPermissonTypeEnum.SELF_AND_SUB.getCode())
                && StringUtils.isNotBlank(record.getDeptCode()) ){

            String traceIds = StringUtils.isBlank(deptPO.getTraces())? deptPO.getDepartCode(): deptPO.getTraces()+ ViewConstant.COMMA_DISP+  deptPO.getDepartCode();
            List<SysDepartPO> poList= sysDeptDAO.findSubList(traceIds);
            if(!CollectionUtils.isEmpty(poList)){
                user.setScopeDeptIdList(poList.stream().map(SysDepartPO::getDepartCode).collect(Collectors.toList()));
            }
        }else if(CompareUtil.isEqual(record.getScopeType(), DataPermissonTypeEnum.DEPT_AND_SUB.getCode())
                && StringUtils.isNotBlank(record.getDeptCode()) ){
            List<Object> scopeDeptIdList = Lists.newArrayList();
            scopeDeptIdList.add(record.getDeptCode());

            String traceIds = StringUtils.isBlank(deptPO.getTraces())? deptPO.getDepartCode(): deptPO.getTraces()+ ViewConstant.COMMA_DISP+  deptPO.getDepartCode();
            List<SysDepartPO> poList= sysDeptDAO.findSubList(traceIds);

            if(!CollectionUtils.isEmpty(poList)){
                scopeDeptIdList.addAll(poList.stream().map(SysDepartPO::getDepartCode).collect(Collectors.toList()));
            }
            user.setScopeDeptIdList(scopeDeptIdList);
        }

        List<SysUserRolePO> userRolePOList= sysUserRoleDAO.findList(SysUserRolePO::getUserId, record.getId());
        if(!CollectionUtils.isEmpty(userRolePOList)){
            List<Long> roleIdListAll = userRolePOList.stream().map(SysUserRolePO::getRoleId).collect(Collectors.toList());
            List<SysRolePO> rolePOList= sysRoleDAO.findByIdList(roleIdListAll);
            if(!CollectionUtils.isEmpty(rolePOList)){
                List<Long> roleIdList = rolePOList.stream().map(SysRolePO::getId).collect(Collectors.toList());
                user.setRoleIdList(roleIdList);
                List<SysRoleResourcesPO>  roleResourcesPOList= sysRoleResourcesDAO.findIn(SysRoleResourcesPO::getRoleId,roleIdList);
                if(!CollectionUtils.isEmpty(roleResourcesPOList)){
                    List<Long> resIdList=  roleResourcesPOList.stream().map(SysRoleResourcesPO::getResId).collect(Collectors.toList());
                    List<SysResourcesPO> sysResourcesPOList= sysResourcesDAO.findBtnUriList(resIdList);
                    if(!CollectionUtils.isEmpty(sysResourcesPOList)){
                        Set<String> btnUrlSet = Sets.newHashSet();
                        for(SysResourcesPO sysResourcesPO: sysResourcesPOList){
                            if(StringUtil.isNotBlank(sysResourcesPO.getUri())){
                                for(String uri: sysResourcesPO.getUri().split(",")){
                                    btnUrlSet.add(uri);
                                }
                            }
                        }
                        user.setBtnUrls(btnUrlSet);
                    }
                }
            }
        }

        String accessToken = ApiContext.setCurrentUser(user, ContextHolder.getClientId());
        user.setAccessToken(accessToken);
        return user;
    }

    /**
     * 用户登录失败次数记录KEY
     * @param username
     * @return
     */
    public String loginFailCountKey(String username){
        return CacheKeyConstant.LOGIN_FAIL_PREFIX_KEY+":"+ username;
    }

    /**
     * 校验密码
     * @param password
     * @param dbpassword
     * @return
     */
    public Boolean checkPassword(String password,String dbpassword){
        String depwd = PasswordUtils.encrypt(password);
        return depwd.equals(dbpassword);
    }

    @Override
    public Boolean checkPicVerifyCode(String verifyToken, String verificationCode) {
        String key = this.getVerifyCodeKey(verifyToken, VerifyCodeType.PIC.getCode());
        log.info("=================checkPicVerifyCode, key:{}", key);
        RBucket<String> bucket = redissonClient.getBucket(key);
        String picVc = bucket.get();
        if (StringUtils.isBlank(picVc)) {
            // 未获取到图片验证码的情况
            return Boolean.FALSE;
        }
        RAtomicLong atomicLong = redissonClient.getAtomicLong(key + "_failures");
        if (!picVc.equalsIgnoreCase(verificationCode)) {
            // 校验失败的情况，记录校验失败次数，超过5次后删除该图片验证码，以防止图片验证码被无限重试后破解掉
            if (atomicLong.addAndGet(1) > 5) {
                // 删除该图片验证码 同时删除验证码错误次数缓存信息
                bucket.delete();
                atomicLong.delete();
            } else {
                atomicLong.expire(60, TimeUnit.SECONDS);
            }
            return Boolean.FALSE;
        }
        // 校验成功后马上删除该图片验证码 同时删除验证码错误次数缓存信息
        bucket.delete();
        atomicLong.delete();
        return Boolean.TRUE;
    }

    @Override
    public Result<Boolean> logout() {
        ApiContext.removeCurrentUser();
        return Result.success().data(Boolean.TRUE).build();
    }

    @Override
    public Result<SysVerifyCodeVO> getImageVerify() {
        return Result.success().data(getPicVerificationCode()).build();
    }

    private SysVerifyCodeVO getPicVerificationCode(){
        String verifyToken= "VC" + UUIDUtil.createOrderNo();
        VCodeGenerator vc = new VCodeGenerator();
        String verifyCode= vc.generatorVCode();
        String key = this.getVerifyCodeKey(verifyToken, VerifyCodeType.PIC.getCode());
        RBucket<String> bucket= redissonClient.getBucket(key);
        // 1分钟内有效
        bucket.set(verifyCode, 60, TimeUnit.SECONDS);


        BufferedImage image= vc.generatorVCodeImage(verifyCode,true);
        String base64Str=vc.image2Base64(image,"gif");
        if(StringUtils.isBlank(base64Str)){
            throw new ApiException("系统内部异常");
        }
        SysVerifyCodeVO vo=new SysVerifyCodeVO();
        vo.setVerifyToken(verifyToken);
        vo.setVerifyCodeBase64Str("data:image/gif;base64,"+base64Str);
        return vo;
    }
    private String getVerifyCodeKey(String verifyToken, Integer verifyCodeType){
        return CacheKeyConstant.VERIFY_CODE_PREFIX_KEY  + ":" + verifyCodeType + ":" + verifyToken;
    }

    @Override
    public Result<List<SysSubVO>> findSysList() {
        List<SysSubVO> list = Lists.newArrayList();
        List<SysIndexPO> poList = sysIndexDAO.findList();
        if(!CollectionUtils.isEmpty(poList)){
            list = ListUtil.copyList(poList, SysSubVO.class);
        }
        // TODO: 2024/7/31 用户权限模块 跳转站点 siteUrl+权限菜单的第一个 ，如果siteUrl设置了固定的菜单路由地址(#/role),就直接去这个地址
        return Result.success().data(list).build();
    }

    @Override
    public Result<SysSubResVO> res(String sysCode) {
        SysIndexPO sysIndexPO = sysIndexDAO.findOne(sysCode);
        if(sysIndexPO==null){
            return Result.failure().message("子系统记录不存在").build();
        }

        User user= ApiContext.getCurrentUser();

        SysSubResVO vo = new SysSubResVO();
        vo.setSysCode(sysIndexPO.getSysCode());
        vo.setSysName(sysIndexPO.getSysName());

        List<SysResourcesPO> poList = new ArrayList<>();
        if(CompareUtil.isEqual(user.getAccType(), UserAccTypeEnum.SYS_MANAGER.getType())){
            poList=  sysResourcesDAO.findList(sysCode, Boolean.TRUE);
        }else{
            Map<String,List<String>> btnMap = Maps.newHashMap();
            List<Long> resIdList = getResIdList(user.getUserId(),user.getRoleIdList());
            if(!CollectionUtils.isEmpty(resIdList)){
                List<SysResourcesPO> allPoList=  sysResourcesDAO.findCondition(sysCode,resIdList, null);
                Map<Long,SysResourcesPO> poMap = allPoList.stream().collect(Collectors.toMap(SysResourcesPO::getId,SysResourcesPO->SysResourcesPO));
                for(SysResourcesPO po: allPoList){
                    if(CompareUtil.isEqual(po.getResType(), ResTypeEnum.menu.getCode()) || CompareUtil.isEqual(po.getResType(), ResTypeEnum.directory.getCode())){
                        poList.add(po);
                    }else if(CompareUtil.isEqual(po.getResType(), ResTypeEnum.button.getCode())){
                        if(StringUtils.isBlank(po.getCode())){
                            log.debug("按钮的权限码未配置,resId:{}",po.getId());
                            continue;
                        }
                        SysResourcesPO parentPo= poMap.get(po.getPid());
                        if(parentPo ==null || StringUtils.isBlank(parentPo.getRouter())){
                            log.debug("父节点或者父节点的路由地址为空, resId: {}",po.getId());
                            continue;
                        }
                        List<String> codeList = btnMap.get(parentPo.getRouter());
                        if(CollectionUtils.isEmpty(codeList)){
                            codeList = new ArrayList<>();
                        }
                        codeList.add(po.getCode());
                        btnMap.put(parentPo.getRouter(),codeList);
                    }
                }
                vo.setBtnMap(btnMap);
            }
        }

        if(!CollectionUtils.isEmpty(poList)){
            List<SysResourcesVO> resList = ListUtil.copyList(poList, SysResourcesVO.class);

            TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
            treeNodeConfig.setIdKey("id");
            treeNodeConfig.setParentIdKey("pid");
            treeNodeConfig.setNameKey("name");
            treeNodeConfig.setWeightKey("sortIndex");

            List<Tree<Long>> trees = TreeUtil.build(resList, null,treeNodeConfig,
                    (po, tree) -> {
                        tree.setId(po.getId());
                        tree.setName(po.getName());
                        tree.setParentId(po.getPid());
                        tree.setWeight(po.getSortIndex());
                        tree.putExtra("extra", po);
                    });
            vo.setTrees(trees);
        }
        return Result.success().data(vo).build();
    }

    private List<Long> getResIdList(Long uid, List<Long> roleIdList){
        if(CollectionUtils.isEmpty(roleIdList)){
            log.info("getResIdList>>>roleIdList is null, uid:{}",uid);
            return null;
        }
        List<SysRoleResourcesPO> roleResourcesPOList = sysRoleResourcesDAO.findIn(SysRoleResourcesPO::getRoleId,roleIdList);
        if(CollectionUtils.isEmpty(roleResourcesPOList)){
            log.info("getResIdList>>>role-res is null, uid:{}",uid);
            return null;
        }
        return roleResourcesPOList.stream().map(SysRoleResourcesPO::getResId).collect(Collectors.toList());
    }


}
